#include "defines.h"
#include "kozos.h"
#include "netdef.h"
#include "netlib.h"
#include "ip.h"
#include "ping.h"
#include "lib.h"

static kz_msgbox_id_t ping_mbox;

typedef struct {
    uint8 IP[4];
    uint16 size;
} ping_send_param;

void ping_init(void)
{
    ping_mbox = kz_getmsgbox();
}

int ping_send(const uint8 dst_IP[], uint16 size)
{
    ping_send_param *param = kz_kmalloc(sizeof(ping_send_param));
    memcpy(param->IP, dst_IP, 4);
    param->size = size;
    if (kz_send(ping_mbox, 's', (char *)param, 0, NET_MBOXSIZE) == -1) {
        kz_kmfree(param);
        return -1;
    }
    return 0;
}

void ping_handle(uint8 *packet)
{
    ASSERT(ping_mbox != MSGBOX_NULL, "no ping_mbox");
    if (kz_send(ping_mbox, 'h', (char *)packet, 1, NET_MBOXSIZE) == -1) {
        kz_kmfree(packet);
    }
}

static void send_ping_request(const uint8 dst_IP[], uint16 size)
{
    static uint16 ping_id = 0;
    ping_header *phdr = (ping_header *)kz_kmalloc(sizeof(ping_header) + size);
    uint8 *p;
    uint16 i;

    phdr->icmp_type = 8;
    phdr->icmp_code = 0;
    phdr->icmp_checksum = 0;
    phdr->echo_id = ping_id++;
    phdr->echo_sequence_number = 0;
    for (i = 0, p = (uint8 *)(phdr + 1); i < size; ) {
        *p++ = i++;
    }
    phdr->icmp_checksum = ip_checksum((uint8 *)phdr, sizeof(ping_header) + size);

    ip_send_data((uint8 *)phdr, sizeof(ping_header) + size, IP_PROTOCOL_ICMP, dst_IP);
    kz_kmfree(phdr);
}

static void receive_ping_reply(uint8 *packet)
{
    ip_header *ihdr = (ip_header *)(packet + sizeof(ethernet_header));
    ping_header *phdr = (ping_header *)(ihdr + 1);
    uint16 i, size = ihdr->ip_total_length - sizeof(*ihdr) - sizeof(*phdr);
    uint8 c = 0, *p = (uint8 *)(phdr + 1);

    for (i = size; i > 0; i--) {
        if (*p++ != c++)
            break;
    }

    putval(size, 0);
    puts(" bytes from ");
    print_IP(ihdr->ip_src_IP);
    if (i > 0) puts(" !"); // データ違う
    puts("\n");
}

void ping_main(int argc, char *argv)
{
    int cmd;
    char *p;

    while (1) {
        kz_recv(ping_mbox, &cmd, &p);

        if (cmd == 's') {
            ping_send_param *param = (ping_send_param *)p;
            send_ping_request(param->IP, param->size);
            kz_kmfree(param);

        } else if (cmd == 'h') {
            receive_ping_reply((uint8 *)p);
            kz_kmfree(p);

        } else {
            ASSERT(0, "ping: Bad cmd");
        }
    }
}
